package cn.wen.mybatis.framework.binding;

import cn.wen.mybatis.framework.reflection.ExceptionUtil;
import cn.wen.mybatis.framework.session.SqlSession;

import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;

/**
 * Mybatis使用了动态代理减少了错误的发生，并且让api变的更简洁易用
 * 调用的方法有可能写错，实际要执行的sql并没有配置
 * 传入的参数有可能写错，因为入参是Object类型
 * 返回值有可能写错，因为返回值也是Object类型
 * 所有使用代理模式可以接受返回值
 * 利用MapperProxyFactory生成MapperProxy，而MapperProxy实现了InvocationHandler，
 * 所以返回的代理类就是MapperProxy，所以当执行Mapper接口的返回时，会首先进入MapperProxy#invoke方法
 *
 * 同时MapperProxy包装了SqlSession，从这我们就能猜到，后续MapperProxy肯定是利用调用的方法，
 * 拼装sql对应的id，然后交给SqlSession来执行查询
 */
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 是Interface接口还是Object类
      // 如果方法是Object类自带的方法，比如没有被重写的equals toString, hashcode 等，还是执行原来的方法 不需要拦截
      // getDeclaringClass()返回表示声明由此 Method 对象表示的方法的类或接口的 Class 对象。
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
        // 调用接口的默认方法
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }

    // 如果不是object的自带方法，先去  Map<Method, MapperMethod> methodCache中找是否已经存在这个method了，
    // 没有就将method封装成MapperMethod存进methodCache中然后返回MapperMethod。
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 执行sqlSession的方法
     return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
     return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }

  private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
      throws Throwable {
    final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
        .getDeclaredConstructor(Class.class, int.class);
    if (!constructor.isAccessible()) {
      constructor.setAccessible(true);
    }
    final Class<?> declaringClass = method.getDeclaringClass();
    return constructor
        .newInstance(declaringClass,
            MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
        .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
  }

  /**
   * Backport of java.lang.reflect.Method#isDefault()
   */
  private boolean isDefaultMethod(Method method) {
    return (method.getModifiers()
        & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
        && method.getDeclaringClass().isInterface();
  }
}
